
#include <dev.h>
#include "pci.h"
#include "i2c.h"

/* ------------------------------------------------------------------------- */

/* for global status */
#define ABRT_STS		0x0001		/* abort status */
#define COL_STS			0x0002		/* collision status */
#define PRERR_STS		0x0004		/* protocol error status */
#define HST_STS			0x0008		/* host controller busy */
#define HCYC_STS		0x0010		/* host cycle complete status */
#define TO_STS			0x0020		/* time out error status */
#define SNP_STS			0x0100		/* snoop address match status */
#define HSLV_STS		0x0200		/* host-as-slave address match status */
#define SNBA_STS		0x0400		/* SM Bus alert status */
#define SM_BSY			0x0800		/* SM Bus busy */

/* for global enable register */
#define CYC_TYPE		0x0007		/* cycle type */
#define HOST_STC		0x0008		/* host start command */
#define HCYC_EN			0x0010		/* host cycle interrupt enable */
#define ABORT			0x0020		/* abort current host command */
#define SNP_EN			0x0100		/* snoop address match enable */
#define HSLV_EN			0x0200		/* host-as-slave address match enable */
#define SNBA_EN			0x0400		/* SM Bus alert enable */

/* for host address register */
#define RC				0x0001		/* read cycle */
#define HSTADDR			0x00fe		/* host cycle address */
#define HST10BA			0xff00		/* host 10 bit address LSBs */

/* cycle type encoding */
#define QUICK_COMMAND	0x0			/* quick command */
#define SEND_BYTE		0x1			/* receive or send byte */
#define ACCESS_BYTE		0x2			/* read or write byte */
#define ACCESS_WORD		0x3			/* read or write word */
#define PROCESS_CALL	0x4			/* process call */
#define ACCESS_BLOCK	0x5			/* read or write block */

/* ------------------------------------------------------------------------- */

typedef struct {
	uint16 status;					/* global status */
	uint16 enable;					/* global enable register */
	uint16 address;					/* host address register */
	uint16 data;					/* host data register */
	uint8 command;					/* host command field register */
	uint8 block;					/* host block data fifo access port */
	uint16 has_data;				/* host-as-slave data register */
	uint16 has_device;				/* host-as-slave device address register */
	uint8 has_host;					/* host-as-slave host address register */
	uint8 snoop;					/* snoop address register */
} smbus_reg_t;

/* ------------------------------------------------------------------------- */

static const uint8 io_r_mask[] = {
	0xff, 0xff, 0xd7, 0xff, 0xff, 0xff, 0xff, 0xff,
	0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};

static const uint8 io_w_mask[] = {
	0xc0, 0xf0, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff,
	0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
};

static const uint8 io_c_mask[] = {
	0x37, 0x07, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};

/* ------------------------------------------------------------------------- */

static pci_dev_t smbus_dev;
static smbus_reg_t reg;

static void fastcall smbus_config(uint8 reg, uint32 data, uint32 mask);
static size_t fastcall smbus_map_size(uint map);
static size_t fastcall smbus_read_map(uint map, uint32 addr, void *data, size_t size);
static size_t fastcall smbus_write_map(uint map, uint32 addr, const void *data, size_t size);

static size_t fastcall read_con_reg(uint16 addr, void *data, size_t size);
static size_t fastcall write_con_reg(uint16 addr, const void *data, size_t size);

static inline uint16 get_host_addr(void);
static void host_start_command(void);

/* ------------------------------------------------------------------------- */

pci_dev_t *get_smbus_device(void)
{
	memset(&smbus_dev, 0, sizeof(smbus_dev));
	memset(&reg, 0, sizeof(reg));

#ifndef NDEBUG
	strcpy(smbus_dev.name, "SMBUS");
#endif

	smbus_dev.conf.hdr.type0.base_addr[1] = PCI_BASE_ADDR_IO;
	smbus_dev.conf.hdr.type0.base_addr[2] = PCI_BASE_ADDR_IO;

	smbus_dev.config = &smbus_config;
	smbus_dev.map_size = &smbus_map_size;
	smbus_dev.read_map = &smbus_read_map;
	smbus_dev.write_map = &smbus_write_map;

	return &smbus_dev;
}

/* ------------------------------------------------------------------------- */

static void fastcall smbus_config(uint8 reg, uint32 data, uint32 mask)
{

}

static size_t fastcall smbus_map_size(uint map)
{
	if (map == 1 || map == 2)
		return 0x100;

	return 0;
}

static size_t fastcall smbus_read_map(uint map, uint32 addr, void *data, size_t size)
{
	if (map != 1)
		return size;

	return read_con_reg(addr, data, size);
}

static size_t fastcall smbus_write_map(uint map, uint32 addr, const void *data, size_t size)
{
	if (map != 1)
		return size;

	return write_con_reg(addr, data, size);
}

/* ------------------------------------------------------------------------- */

static size_t fastcall read_con_reg(uint16 addr, void *data, size_t size)
{
	size_t rd_size;
	uint8 *wp;
	const uint8 *rp, *r_mask;

	if (addr >= sizeof(reg)) {
		assert(0);
		return size;
	}

	rd_size = sizeof(reg) - addr;
	if (size > rd_size)
		size = rd_size;

	r_mask = io_r_mask + addr;
	rp = (uint8 *)&reg + addr;
	wp = data;

	for (rd_size = 0; rd_size < size; rd_size++)
		*wp++ = *rp++ & *r_mask++;

	return rd_size;
}

static size_t fastcall write_con_reg(uint16 addr, const void *data, size_t size)
{
	size_t wrt_size;
	uint8 *wp;
	const uint8 *rp, *w_mask, *c_mask;

	if (addr >= sizeof(reg)) {
		assert(0);
		return size;
	}

	wrt_size = sizeof(reg) - addr;
	if (size > wrt_size)
		size = wrt_size;

	w_mask = io_w_mask + addr;
	c_mask = io_c_mask + addr;
	rp = data;
	wp = (uint8 *)&reg + addr;

	for (wrt_size = 0; wrt_size < size; wrt_size++, rp++, wp++, w_mask++, c_mask++)
		*wp = combine_data(*wp, (*rp & *w_mask) & ~(*rp & *c_mask), *w_mask | *c_mask);

	if (reg.enable & HOST_STC) {
		reg.enable &= ~HOST_STC;
		host_start_command();
		reg.status |= HCYC_STS;
	}

	return wrt_size;
}

static inline uint16 get_host_addr(void)
{
	uint16 addr;

	addr = (reg.address & HSTADDR) >> 1;

	if ((addr & 0x7c) != 0x78)
		return addr;

	addr = reg.address;
	return ((addr & HST10BA) >> 6) | ((addr & 0x0006) >> 1);
}

static void host_start_command(void)
{
	size_t size;
	uint16 addr;

	switch (reg.enable & CYC_TYPE) {
	case QUICK_COMMAND:
	case SEND_BYTE:
		assert(0);
		return;
	case ACCESS_BYTE:
		size = 1;
		break;
	case ACCESS_WORD:
		size = 2;
		break;
	case PROCESS_CALL:
	case ACCESS_BLOCK:
		assert(0);
		return;
	}

	addr = get_host_addr();

	if (reg.address & RC)
		i2c_device_read(addr, reg.command, &reg.data, size);
	else
		i2c_device_write(addr, reg.command, &reg.data, size);
}

/* ------------------------------------------------------------------------- */
